package info.openrocket.core.rocketcomponent;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Class that defines different cluster configurations available for the
 * InnerTube.
 * The class is immutable, and all the constructors are private. Therefore the
 * only
 * available cluster configurations are those available in the static fields.
 * 
 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
 */
public class ClusterConfiguration {
	// Helper vars
	private static final double R5 = 1.0 / (2 * Math.sin(2 * Math.PI / 10));
	private static final double SQRT2 = 1.4142135623730951;
	private static final double SQRT3 = 1.7320508075688772;

	/** A single motor */
	public static final ClusterConfiguration SINGLE = new ClusterConfiguration("single", 0, 0);

	/** Definitions of cluster configurations. Do not modify array. */
	public static final ClusterConfiguration[] CONFIGURATIONS = {
			// Single row
			SINGLE,
			new ClusterConfiguration("double", -0.5, 0, 0.5, 0),
			new ClusterConfiguration("3-row", -1.0, 0, 0.0, 0, 1.0, 0),
			new ClusterConfiguration("4-row", -1.5, 0, -0.5, 0, 0.5, 0, 1.5, 0),

			// Ring of tubes
			new ClusterConfiguration("3-ring", -0.5, -1.0 / (2 * SQRT3),
					0.5, -1.0 / (2 * SQRT3),
					0, 1.0 / SQRT3),
			new ClusterConfiguration("4-ring", -0.5, 0.5, 0.5, 0.5, 0.5, -0.5, -0.5, -0.5),
			new ClusterConfiguration("5-ring", 0, R5,
					R5 * Math.sin(2 * Math.PI / 5), R5 * Math.cos(2 * Math.PI / 5),
					R5 * Math.sin(2 * Math.PI * 2 / 5), R5 * Math.cos(2 * Math.PI * 2 / 5),
					R5 * Math.sin(2 * Math.PI * 3 / 5), R5 * Math.cos(2 * Math.PI * 3 / 5),
					R5 * Math.sin(2 * Math.PI * 4 / 5), R5 * Math.cos(2 * Math.PI * 4 / 5)),
			new ClusterConfiguration("6-ring", 0, 1, SQRT3 / 2, 0.5, SQRT3 / 2, -0.5,
					0, -1, -SQRT3 / 2, -0.5, -SQRT3 / 2, 0.5),

			// Centered with ring
			new ClusterConfiguration("3-star", 0, 0, 0, 1, SQRT3 / 2, -0.5, -SQRT3 / 2, -0.5),
			new ClusterConfiguration("4-star", 0, 0, -1 / SQRT2, 1 / SQRT2, 1 / SQRT2, 1 / SQRT2,
					1 / SQRT2, -1 / SQRT2, -1 / SQRT2, -1 / SQRT2),
			new ClusterConfiguration("5-star", 0, 0, 0, 1,
					Math.sin(2 * Math.PI / 5), Math.cos(2 * Math.PI / 5),
					Math.sin(2 * Math.PI * 2 / 5), Math.cos(2 * Math.PI * 2 / 5),
					Math.sin(2 * Math.PI * 3 / 5), Math.cos(2 * Math.PI * 3 / 5),
					Math.sin(2 * Math.PI * 4 / 5), Math.cos(2 * Math.PI * 4 / 5)),
			new ClusterConfiguration("6-star", 0, 0, 0, 1, SQRT3 / 2, 0.5, SQRT3 / 2, -0.5,
					0, -1, -SQRT3 / 2, -0.5, -SQRT3 / 2, 0.5),
			new ClusterConfiguration("9-grid", -1.4, 1.4, 0, 1.4, 1.4, 1.4,
					-1.4, 0, 0, 0, 1.4, 0,
					-1.4, -1.4, 0, -1.4, 1.4, -1.4),
			new ClusterConfiguration("9-star", 0, 0,
					1.4, 0, 1.4 / SQRT2, -1.4 / SQRT2, 0, -1.4, -1.4 / SQRT2, -1.4 / SQRT2,
					-1.4, 0, -1.4 / SQRT2, 1.4 / SQRT2, 0, 1.4, 1.4 / SQRT2, 1.4 / SQRT2)
	};

	private final List<Double> points;
	private final String xmlName;

	private ClusterConfiguration(String xmlName, double... points) {
		this.xmlName = xmlName;
		if (points.length == 0 || points.length % 2 == 1) {
			throw new IllegalArgumentException("Illegal number of points specified: " +
					points.length);
		}
		List<Double> l = new ArrayList<>(points.length);
		for (double d : points)
			l.add(d);

		this.points = Collections.unmodifiableList(l);
	}

	public String getXMLName() {
		return xmlName;
	}

	public int getClusterCount() {
		return points.size() / 2;
	}

	/**
	 * Returns the relative positions of the cluster components. The list is of
	 * length
	 * <code>2*getClusterCount()</code> with (x,y) value pairs. The origin is at
	 * (0,0)
	 * and the values are positioned so that the closest clusters have distance of
	 * 1.
	 * 
	 * @return a list of (x,y) coordinate pairs.
	 */
	public List<Double> getPoints() {
		return points; // Unmodifiable
	}

	/**
	 * Return the points rotated by <code>rotation</code> radians.
	 * 
	 * @param rotation Rotation amount.
	 */
	public List<Double> getPoints(double rotation) {
		double cos = Math.cos(rotation);
		double sin = Math.sin(rotation);
		List<Double> ret = new ArrayList<>(points.size());
		for (int i = 0; i < points.size() / 2; i++) {
			double x = points.get(2 * i);
			double y = points.get(2 * i + 1);
			ret.add(x * cos + y * sin);
			ret.add(-x * sin + y * cos);
		}
		return ret;
	}

	@Override
	public String toString() {
		return xmlName;
	}
}
